package gen

import (
	"errors"
	"fmt"
	"log"
	"os"
	"sort"
	"strings"
	"text/template"
	"time"

	"code_gen/entity"
	"code_gen/logger"
	"github.com/Masterminds/sprig/v3"

	"code_gen/config"
	dlc "code_gen/dialect"
	"code_gen/utils"
)

// Gen / 生成代码
func Gen() {

	// 模板语法 https://pkg.go.dev/text/template

	// 读取配置的项目
	projects := config.Cfg.Projects
	if projects == nil || len(projects) == 0 {
		log.Panic("请配置项目")
	}

	// 生成代码按项目
	for _, prj := range projects {
		ctx := dlc.NewContext()
		ctx.Prj = prj
		err := Exec(ctx, prj.DataSource, prj.Template, prj.DataTypeMap, prj.Ext)
		if err != nil {
			log.Panic("生成代码失败: ", prj.Name, err)
		}
	}
}

// Exec / 执行生成代码
// @param ctx 上下文
// @param ds 数据源
// @param tpl 模板
// @param dataTypeMap 数据类型映射
// @param ext 扩展
func Exec(ctx *dlc.Context, ds *config.DataSource, tpl *config.Template, dataTypeMap []*config.DataTypeMap, ext map[string]interface{}) error {
	// 设置上下文
	ctx.Ds = ds
	ctx.Tpl = tpl
	ctx.Ext = ext

	// 获取方言
	dialect := dlc.GetDialect(ds.Dialect)
	if dialect == nil {
		return fmt.Errorf("dialect %s not found", ds.Dialect)
	}
	ctx.Dialect = dialect

	// 注册数据类型
	if dataTypeMap != nil && len(dataTypeMap) > 0 {
		for _, typeMap := range dataTypeMap {
			registerDataType(ctx, typeMap)
		}
	}

	// 文件保存器
	ctx.FileSaver = dlc.GetFileSaver(ds.Lang)

	// 数据库
	db, err := dialect.GetDB(ctx, ds)
	if err != nil {
		return err
	}
	ctx.Db = db

	// 获取表信息
	tables := tpl.Tables
	if tables == nil || len(tables) == 0 {
		return errors.New("tables is empty")
	}
	tfs := tpl.Files
	if tfs == nil || len(tfs) == 0 {
		return errors.New("template files is empty")
	}

	// 模板变量
	tplVar := map[string]interface{}{}
	tplVar["ctx"] = ctx
	tplVar["ds"] = ds
	tplVar["dialect"] = dialect
	tplVar["tpl"] = tpl
	tplVar["tplVars"] = tpl.Vars
	tplVar["tplFiles"] = tpl.Files
	tplVar["ext"] = ext
	tplVar["datetime"] = time.Now().Format(time.DateTime)
	tplVar["jdbcVars"] = dlc.GetJavaJdbcType()
	ctx.TplVar = tplVar

	// 模板函数
	fnMap := sprig.FuncMap()
	fnMap["toCamel"] = utils.ToCamel
	fnMap["toCamelLower"] = utils.ToCamelLower
	fnMap["mapSet"] = utils.MapSet
	fnMap["mapGet"] = utils.MapGet
	fnMap["mapExists"] = utils.MapExists
	tplExecutor := template.New("tpl").Funcs(fnMap)
	ctx.TplExecutor = tplExecutor

	// 按表生成文件
	for _, table := range tables {
		err = generateTable(ctx, table, tplVar)
		if err != nil {
			return err
		}
	}

	return nil
}

// 注册数据类型
func registerDataType(ctx *dlc.Context, m *config.DataTypeMap) {
	dlc.RegisterDataTypeMapping(m.Dialect, m.Lang, m.DbDataType, m.LangDataType, m.LangPackage)
}

// 生成表
func generateTable(ctx *dlc.Context, table string, tplVar map[string]interface{}) error {
	tableInfo, err := ctx.Dialect.GetTableInfo(ctx, ctx.Ds, ctx.Db, table)
	if err != nil {
		return err
	}
	tplVar["table"] = tableInfo

	// 排序文件生成顺序
	sort.Sort(config.TemplateFileOrder(ctx.Tpl.Files))

	for _, tf := range ctx.Tpl.Files {
		err = generateFile(ctx, tf, tplVar)
		if err != nil {
			return err
		}
	}

	return nil
}

// 生成文件
func generateFile(ctx *dlc.Context, tf *config.TemplateFile, tplVar map[string]interface{}) error {

	tableInfo := tplVar["table"].(*entity.TableInfo)
	logger.Log.Info("项目：", ctx.Prj.Name, " 渲染模板: ", tf.Path, " 表: ", tableInfo.Name)

	tplContent, err := getTemplateContent(tf)
	if err != nil {
		return err
	}
	tpl, err := ctx.TplExecutor.Parse(tplContent)
	if err != nil {
		return err
	}

	// 设置文件名为空，避免干扰下一文件的输出
	ctx.Set("fileName", "")

	tplVar["tf"] = tf
	var writer strings.Builder
	err = tpl.Execute(&writer, tplVar)
	if err != nil {
		return err
	}

	err = ctx.FileSaver.GenFile(ctx, tf, writer.String())
	if err != nil {
		return err
	}

	return nil
}

// 获取模板内容
func getTemplateContent(tf *config.TemplateFile) (string, error) {
	if tf.Content != "" {
		return tf.Content, nil
	}

	bts, err := os.ReadFile(tf.Path)
	if err != nil {
		return "", err
	}
	return string(bts), nil
}
